#include "threadpool.h"
#include <iostream>
#include <cstring>
#include <unistd.h>

using namespace std;

#define DEFAULT_MIN_THREADS 3
#define DEFAULT_MAX_THREADS 8
#define DEFAULT_MAX_QUEUE_CAPACITY 10
#define SLEEP_INTERVAL      5
#define INCREASE_NUM        2
#define DECREASE_NUM        2

/* 线程退出 */
void ThreadPool::exitThread( pthread_t tid)
{
    for(int idx = 0; idx < this->m_maxThreadNum; idx++)
    {
        //存储线程id的那个数组里面的元素是不是等于tid
        if(m_threadId[idx] == tid)  //tid对应的线程要退出
        {
            m_threadId[idx] = 0;

            /* 线程退出 */
            pthread_exit(NULL);
        }
    }

}

/* 管理者线程 */
void * managerThreadFunc(void * arg)
{

    /* 线程分离 */
    pthread_detach(pthread_self());

    ThreadPool * pool = (ThreadPool *) arg;

    /* 调度，管理线程也要一直干活，直到线程池释放 */
    while(true)
    {
        /* 每隔n秒检测一次 */
        sleep(SLEEP_INTERVAL);

        // 取出线程池中任务的数量和当前线程的数量
        /* 加锁是因为防止其他线程同时访问这些变量 */
        pthread_mutex_lock(&(pool->m_mutex));
        int aliveThreadNum = pool->m_aliveThreadNum;
        int queueSize = pool->m_queueSize;
        pthread_mutex_unlock(&(pool->m_mutex));

        // 取出线程池中忙的线程的数量
        // 因为busyNum经常被访问，为了提高操作效率，所以单独锁住busyNum，有时候就不用锁整个线程池了
        pthread_mutex_lock(&(pool->m_busyMutex));
        int busyThreadNum = pool->m_busyThreadNum;
        pthread_mutex_unlock(&(pool->m_busyMutex));

        //cout << "queueSize:" << queueSize << ", aliveThreadNum:" << aliveThreadNum << ", busyThreadNum" << busyThreadNum  << endl;

        // 可以开始添加或销毁线程了
        /* 1.什么时候需要增加线程 */
        // 任务的个数>剩余的存活的线程个数 && 存活的线程数<最大线程数
        if(queueSize > (aliveThreadNum - busyThreadNum) && aliveThreadNum < pool->m_maxThreadNum)
        {
            /* 标记增加的数量 */
            int addCnt = 0;
            /* 加锁：防止其他线程更改要使用的变量liveNum */
            pthread_mutex_lock(&(pool->m_mutex));
            /* 直接增加到线程上线值 */
            for(int idx = 0; idx < pool->m_maxThreadNum; idx++)
            {
                /* 探测哪一个位置可以存储线程id */
                if(pool->m_threadId[idx] == 0)
                {
                    /* 所以在这个位置进行创建线程id */
                    int ret = pthread_create(&(pool->m_threadId[idx]), NULL, workerThreadFunc, pool);
                    if(ret == -1)
                    {
                        perror("thread create error.");
                        _exit(-1);
                    }
                    printf("%lu join thread pool.\n", pool->m_threadId[idx]);
                    
                    addCnt++;       
                    /* 存活的线程数++ */
                    (pool->m_aliveThreadNum)++;
                }
            }
            /* 解锁 */
            pthread_mutex_unlock(&(pool->m_mutex));
        }

        /* 2. 什么时候减少线程池的线程呢 */
        // 不加锁是因为minNum是一个一开始设定好的值，不会发生改变
        if(queueSize < aliveThreadNum - busyThreadNum && aliveThreadNum > pool->m_minThreadNum)
        {
            /* 加锁：exitNum */
            pthread_mutex_lock(&(pool->m_mutex));

            /* 一次性要减少的线程数 */
            pool->exitNum = DECREASE_NUM;

            /* 解锁 */
            pthread_mutex_unlock(&(pool->m_mutex));

            /* 管理者函数内部并不能销毁线程，通过发送信号让工作线程自杀 */
            for(int idx = 0; idx < DECREASE_NUM; idx++)
            {
                //空闲线程阻塞在wait，发信号唤醒线程，让线程自己退出
                //一次唤醒一个线程，广播一次唤醒多个线程
                pthread_cond_signal(&(pool->m_queueNotEmpty));  
            }
        }
    }
}

/* 工作者线程 */
void * workerThreadFunc(void * arg)
{
    /* 线程分离模式使得线程在结束后自动回收资源，无需主线程调用 pthread_join()，从而避免阻塞。
   分离的线程会独立运行，终止后系统会自动回收资源，因此它不会阻塞主线程或其他线程；
   线程分离仅管理资源回收，不影响线程的并发执行。 */
    pthread_detach(pthread_self());
    //获得线程池，操作任务队列，可以全局但是不安全
    //为什么传pool，因为需要使用队列，使用pool里面的数据
    ThreadPool * pool = (ThreadPool *) arg;
    while(true)
    {
        /* 加锁 */
        pthread_mutex_lock(&(pool->m_mutex));
        //如果任务队列为零，用while是因为只要任务为0就阻塞
        while(pool->m_queueSize == 0)
        {
            usleep(10000);
            /* 等一个信号,阻塞等待，数据不为空时唤醒并解锁,之后加锁 */
            pthread_cond_wait(&(pool->m_queueNotEmpty), &(pool->m_mutex));

            /* 线程被唤醒之后，判断是否要销毁线程 */
            if(pool->exitNum > 0)
            {
                pool->exitNum--;    //不能放进下面的if语句，不然线程可能不回去干活而是自杀
                if(pool->m_aliveThreadNum > pool->m_minThreadNum)
                {
                    /* 因为线程退出了，所以存活的线程数减一 */
                    (pool->m_aliveThreadNum)--;

                    /* 解锁：不然就死锁了 */
                    pthread_mutex_unlock(&(pool->m_mutex));

                    printf("%lu exit thread pool...\n", pthread_self()); 
                
                    /* 线程退出，线程自杀 */
                    //不能直接使用exit函数，应该设置退出函数，将该pthreadId值修改为0，方便后续重复利用
                    //所以传入参数为要杀死的线程的id
                    pool->exitThread(pthread_self());
                }
            }
        }
        /* 程序到这里说明，工作线程开始消费，从任务队列中取出一个任务 */
        Task_t newTask = pool->m_queue[pool->m_queueFront];     //从头部取出任务
        /* 将队列的队头向后迭代，实现环形队列 */
        pool->m_queueFront = (pool->m_queueFront + 1) % pool->m_queueCapacity;
        /* 队列的任务数减一 */
        pool->m_queueSize--;
        /* 对线程池解锁 */
        pthread_mutex_unlock(&pool->m_mutex);

        //任务执行之前，忙碌线程加一
        pthread_mutex_unlock(&(pool->m_busyMutex));
        pool->m_busyThreadNum++;
        pthread_mutex_unlock(&(pool->m_busyMutex));
        /* 线程开始执行任务 */
        newTask.function(newTask.arg);  //传堆内存比较好，函数执行完对堆内存进行释放，栈内存要保证栈内存一直存在
        //任务执行完毕，忙碌的线程数减一
        pthread_mutex_unlock(&(pool->m_busyMutex));
        pool->m_busyThreadNum--;
        pthread_mutex_unlock(&(pool->m_busyMutex));
        
        /* 在任务函数结束的时候再对生产者发信号 */
        pthread_cond_signal(&pool->m_queueNotFull);
    }

}
/* 构造函数 */
ThreadPool::ThreadPool(int minThread, int maxThread, int maxQueueCapacity)
{
    // 错误判断 
    if(minThread < 0 || maxThread <= 0 || minThread >= maxThread)
    {
        minThread = DEFAULT_MIN_THREADS;
        maxThread = DEFAULT_MAX_THREADS;
    }
    if(maxQueueCapacity <= 0)
    {
        maxQueueCapacity = DEFAULT_MAX_QUEUE_CAPACITY;
    }

    /* 给队列开辟空间 */
    this->m_queue = new Task_t[maxQueueCapacity];
   
    /* 清除内存脏数据 */
    memset(this->m_queue, 0, sizeof(Task_t) * maxQueueCapacity);
    
    // 初始化队列
    /* 队列 */
    this->m_queueCapacity = maxQueueCapacity;
    /* 队列的对头 */
    this->m_queueFront = this->m_queueTail = 0;
    /* 队列的队尾 */
    this->m_queueSize = 0;

    /* 工作线程id储存开辟空间 */
    m_threadId = new pthread_t[maxThread];
    
    /* 清除内存脏数据:初始化为0说明没有被占用，为后续做准备 */
    memset(m_threadId, 0, sizeof(pthread_t) * maxThread);

    this->m_minThreadNum = minThread;
    this->m_maxThreadNum = maxThread;
    this->m_aliveThreadNum = 0;     //存活与最小线程数相等
    this->m_busyThreadNum = 0;
    this->exitNum = 0;                      //需要退出的线程数为0

    /* 初始化锁 */
    pthread_mutex_init(&m_mutex, NULL);
    pthread_mutex_init(&m_busyMutex, NULL);
    /* 初始化条件变量 */
    pthread_cond_init(&m_queueNotEmpty, NULL);
    pthread_cond_init(&m_queueNotFull, NULL);

    int ret = 0;
    /* 创建管理者线程 */
    ret = pthread_create(&m_managerThreadId, NULL, managerThreadFunc, this);    //this是这个类的指针，这样就可以传递整个类过去了
    if(ret == -1)
    {
        perror("thread create error.");
        _exit(-1);
    }

    /* 创建工作线程 */
    for(int idx = 0; idx < minThread; idx++)
    {
        ret = pthread_create(&m_threadId[idx], NULL, workerThreadFunc, this);
        if(ret == -1)
        {
            perror("thread create error.");
            _exit(-1);
        }
        (this->m_aliveThreadNum)++;
    }
}

/* 添加任务 */
void ThreadPool::addTask(void *(*function)(void *arg), void *arg)
{
    /* 将任务添加到任务队列 */
    /* 加锁 */
    pthread_mutex_lock(&m_mutex);
    while(this->m_queueCapacity == this->m_queueSize)
    {
        // 阻塞生产者线程
        pthread_cond_wait(&m_queueNotFull, &m_mutex);
    }

    /* 程序执行到这里:将任务添加到队列里面 */
    m_queue[m_queueTail].function = function; 
    m_queue[m_queueTail].arg = arg;

    m_queueTail = (m_queueTail + 1) % m_queueCapacity;

    /* 任务数量加一 */
    this->m_queueSize++;

    /* 解锁 */
    pthread_mutex_unlock(&m_mutex);
    /* 发信号唤醒消费者 */
    pthread_cond_signal(&m_queueNotEmpty);
}

ThreadPool::~ThreadPool()
{
    if(m_threadId != NULL)
    {
        delete [] m_threadId;
    }

    if(m_queue != NULL)
    {
        delete [] m_queue;
    }

    pthread_mutex_destroy(&m_mutex);
    pthread_mutex_destroy(&m_busyMutex);
    
    pthread_cond_destroy(&m_queueNotEmpty);
    pthread_cond_destroy(&m_queueNotFull);

}